package com.lagou.edu.factory;

import com.lagou.edu.annotations.CustAutowired;
import com.lagou.edu.annotations.CustService;
import com.lagou.edu.annotations.CustTransactional;
import com.mysql.jdbc.StringUtils;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * @author 应癫
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

  /**
   * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合） 任务二：对外提供获取实例对象的接口（根据id获取）
   */
  private static final char PATH_SEPARATOR = '\\';
  private static final char PACKAGE_SEPARATOR = '.';
  public static final String CLASS_FILE_SUFFIX = ".class";
  public static final String JAVA_FILE_SUFFIX = ".java";
  /**
   * 存储对象
   */
  private static Map<String, Object> map = new HashMap<>();

  static {
    //1,获取beans.xml文件流
    InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
    SAXReader saxReader = new SAXReader();
    //2.解析xml
    try {
      Document document = saxReader.read(resourceAsStream);
      Element rootElement = document.getRootElement();
      //获取xml中包扫描配置中的指定包名
      List<Element> componentScanNodes = rootElement.selectNodes("//component-scan");
      Element componentScanNode = componentScanNodes.get(0);
      String basePackage = componentScanNode.attributeValue("base-package");
      System.out.println("-----------------------------basePackage:" + basePackage + ";" + basePackage.replaceAll("./", "//"));

      File baseFile = new File("src/main/java/" + basePackage.replaceAll("\\.", "/"));
      System.out.println(baseFile.getPath());
      List<String> clzNames = new ArrayList<>();
      //2.1.递归调用获取指定包中的.java结尾的文件列表并转为类全路径包名
      findJavaFile(baseFile, basePackage, clzNames);
      //2.2.实例化带有自定义CustService注解的Class并添加到map中（模拟Spring IOC)
      putObjectToMapForService(clzNames);
      //2.3.为在Map带有自定义CustAutowired注解的实例对象设置相应属性值（模拟Spring DI)
      setObjectFieldForAutowired();
      //2.4.为在Map带有自定义CustTransaction注解的实例对象设置事务（模拟Spring 声明式事务)
      setObjectProxyForTransaction();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 为在Map带有自定义CustTransaction注解的实例对象设置事务（模拟Spring 声明式事务)
   */
  private static void setObjectProxyForTransaction() {
    Class<?> clz;
    Set<Object> objSet = new HashSet<>(map.values());
    System.out.println("objSet size:" + objSet.size());
    for (Object obj : objSet) {
      clz = obj.getClass();
      if (clz.isInterface()) {
        continue;
      }
      CustService custService = (CustService) clz.getAnnotation(CustService.class);
      String value = custService.value();
      if (StringUtils.isNullOrEmpty(value)) {
        value = clz.getName();
      }
      if (clz.isAnnotationPresent(CustTransactional.class)) {
        ProxyFactory proxyFactory = (ProxyFactory) map.get("proxyFactory");
        //判断使用JDK动态代理还是Cglib 代理
        Class<?>[] interfaces = clz.getInterfaces();
        if(interfaces == null || interfaces.length ==0){
          map.put(value, proxyFactory.getCglibProxy(obj));
        }else{
          map.put(value, proxyFactory.getJdkProxy(obj));
        }

      }
    }
  }

  /**
   * 为在Map带有自定义CustAutowired注解的实例对象设置相应属性值（模拟Spring DI)
   *
   * @throws IllegalAccessException
   */
  private static void setObjectFieldForAutowired() throws IllegalAccessException {
    Class<?> clz;
    Set<Object> objSet = new HashSet<>(map.values());
    System.out.println("objSet size:" + objSet.size());
    for (Object obj : objSet) {
      clz = obj.getClass();
      if (clz.isInterface()) {
        continue;
      }
      Field[] declaredFields = clz.getDeclaredFields();
      for (Field declaredField : declaredFields) {
        if (declaredField.isAnnotationPresent(CustAutowired.class)) {
          System.out.println("class name:" + clz.getName() + ";fieldName:" + declaredField.getName());
          String fieldClassName = declaredField.getType().getName();
          System.out.println("fieldClassName:" + fieldClassName);
          declaredField.setAccessible(true);
          declaredField.set(obj, map.get(fieldClassName));
        }
      }
    }
  }

  /**
   * 将带有自定义CustService注解的Class实例化并添加到map中
   *
   * @param clzNames
   */
  private static void putObjectToMapForService(List<String> clzNames) throws Exception {
    for (String clzName : clzNames) {
      Class clz = Class.forName(clzName);
      if (clz.isAnnotation()) {
        continue;
      }
      if (clz.isAnnotationPresent(CustService.class)) {
        CustService custService = (CustService) clz.getAnnotation(CustService.class);
        //当value值为空,value值类全路径名称
        String value = custService.value();
        if (StringUtils.isNullOrEmpty(value)) {
          value = clzName;
        }
        Object object = clz.getDeclaredConstructor().newInstance();
        map.put(value, object);
        //如果类实现了接口,将接口的类全路径名称也设置到map中，为Autowired做铺垫
        Class<?>[] interfaceClzs = clz.getInterfaces();
        for (Class interfaceClz : interfaceClzs) {
          map.put(interfaceClz.getName(), object);
        }
      }
    }
  }

  public static void findJavaFile(File rootFile, String prefix, List<String> clzPathList) {
    File[] files = rootFile.listFiles();
    if (files == null || files.length == 0) {
      return;
    }
    for (File file : files) {
      if (file.isDirectory()) {
        findJavaFile(file, prefix, clzPathList);
      } else {
        String clzPath = file.getPath();
        if (clzPath.endsWith(JAVA_FILE_SUFFIX)) {
          clzPath = clzPath.replace(PATH_SEPARATOR, PACKAGE_SEPARATOR);
          int startIndex = clzPath.indexOf(prefix);
          int endIndex = clzPath.lastIndexOf(JAVA_FILE_SUFFIX);
          clzPathList.add(clzPath.substring(startIndex, endIndex));
          System.out.println("file=>" + clzPath.substring(startIndex, endIndex));
        }
      }
    }
  }


  /**
   * 任务二：对外提供获取实例对象的接口（根据id获取）
   *
   * @param id
   * @return
   */
  public static Object getBean(String id) {
    return map.get(id);
  }
}
